﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Linq.Expressions;
using System.Threading;
using System.Web;
using EF4.Core.Repository;
using EF4.Core.Models.Entities;
using System.Reflection;
using EF4.Core.Helpers;
using EF4.Core;


namespace EF4.Repository.EF
{
    public class GenericRepository<T> : IGenericRepository<T> where T : EntityBase
    {

        const string keyPropertyName = "Id";
        /// <summary>
        /// Returns the active object context
        /// </summary>
        private ObjectContext ObjectContext
        {
            get
            {
                return ObjectContextManager.GetObjectContext();
            }
        }

        public T Load(Expression<Func<T, bool>> whereCondition)
        {
            return default(T);
        }

        public IQueryable<T> GetQuery()
        {
            // TODO Check this works with inheritance
            return this.ObjectContext.CreateObjectSet<T>();
        }

        /// <summary>
        /// Provides explicit loading of object properties (EF v4.0 feature)
        /// </summary>
        public void LoadProperty(T entity, Expression<Func<T, object>> selector)
        {
            this.ObjectContext.LoadProperty(entity, selector);
        }

        public List<T> GetAll()
        {
            return GetQuery().ToList();
        }

        public void Add(T entity)
        {
            // TODO - check if this works
            //this.ObjectContext.AddObject(GetEntitySetName<T>(), entity);
            this.ObjectContext.AddObject(GetEntitySetName(GetBaseType(typeof(T))), entity);
        }

        private IQueryable<T> List()
        {
            return ObjectContext.CreateQuery<T>(GetEntitySetName<T>());
        }
        public T Get(int id)
        {
            return GetQuery().FirstOrDefault<T>(CreateGetExpression<T>(id));
        }
        public T Get(string KeyName, object value)
        {
            return GetQuery().FirstOrDefault<T>(CreateGetExpression<T>(KeyName, value));
        }



        public void Update(T entity)
        {

            object  originalItem;
            EntityKey key = ObjectContext.CreateEntityKey(GetEntitySetName(GetBaseType(typeof(T))), entity);
     
            if (ObjectContext.TryGetObjectByKey(key, out originalItem))
            {
                ObjectContext.ApplyPropertyChanges(GetEntitySetName(GetBaseType(typeof(T))), entity);
            }

            //int returnValue = context.SaveChanges();
            //ReleaseObjectContextIfNotReused
            //// TODO - check if this works
            //this.ObjectContext.AttachTo(GetEntitySetName(GetBaseType(typeof(T))), entity);
            //this.ObjectContext.SaveChanges();
        }



        public void Delete(T entity)
        {
            this.ObjectContext.DeleteObject(entity);
        }
        public int Count()
        {
            var query = new ObjectQuery<T>(GetEntitySetName(typeof(T)),
                                            this.ObjectContext,
                                            MergeOption.NoTracking
                                           );
            int count = query.Count();
            return count;
            
        }
        private static Type GetBaseType(Type type)
        {
            Type baseType = type.BaseType;
            if (baseType != null && baseType != typeof(EntityBase))
            {
                return GetBaseType(type.BaseType);
            }
            return type;
        }

        private static bool HasBaseType(Type type, out Type baseType)
        {
            Type originalType = type.GetType();
            baseType = GetBaseType(type);
            return baseType != originalType;
        }

        private Expression<Func<T, bool>> CreateGetExpression<T>(string KeyName, object value)
        {
            ParameterExpression e = Expression.Parameter(typeof(T), "e");

            PropertyInfo propinfo = typeof(T).GetProperty(KeyName);

            MemberExpression m = Expression.MakeMemberAccess(e, propinfo);

            ConstantExpression c = Expression.Constant(value, typeof(int));

            BinaryExpression b = Expression.Equal(m, c);

            Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);

            return lambda;

        }


        protected Expression<Func<T, bool>> CreateGetExpression<T>(int id)
        {

            return CreateGetExpression<T>(keyPropertyName, id);

            //ParameterExpression e = Expression.Parameter(typeof(T), "e");

            //PropertyInfo propinfo = typeof(T).GetProperty(keyPropertyName);

            //MemberExpression m = Expression.MakeMemberAccess(e, propinfo);

            //ConstantExpression c = Expression.Constant(id, typeof(int));

            //BinaryExpression b = Expression.Equal(m, c);

            //Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(b, e);

            //return lambda;

        }



        protected int GetKeyPropertyValue<T>(object entity)
        {

            return (int)typeof(T).GetProperty(keyPropertyName).GetValue(entity, null);

        }

        protected string GetEntitySetName<T>()
        {

            return String.Format("{0}s", typeof(T).Name);

        }

        protected string GetEntitySetName(Type t)
        {
            return String.Format("{0}s", t.Name);

        }

        #region Get List Method

        public IPagedList<T> GetListPaged(int pageIndex, int pageSize, string orderBy, OrderDirections direction )
        {
            var query = GetQuery();
            //query = query.OrderBy(EFHelper.GetField<T>(column)).AsQueryable();
            query = query.OrderBy(orderBy, direction == OrderDirections.DESC);

            return query.ToPagedList(pageIndex, pageSize);
        }

        public IPagedList<T> GetListPaged(int pageIndex, int pageSize, string orderBy, OrderDirections direction, Expression<Func<T, bool>> filter)
        {
            var query = GetQuery();
            query = query.Where(filter);
            query = query.OrderBy(orderBy, direction == OrderDirections.DESC);
            return query.ToPagedList(pageIndex, pageSize);
        }

        #endregion
        private bool _disposed;

        private  void DisposeObject(bool disposing)
        {
            if (_disposed)
            {
                return;
            }
            if (disposing)
            {
                if (this.ObjectContext != null)
                    ObjectContext.Dispose();
            }
            _disposed = true;
        }

        public void Dispose()
        {
            ObjectContext.SaveChanges();
            DisposeObject(true);
            GC.SuppressFinalize(this);
        }
        ~GenericRepository()
        {
        }
    }
}
